/*
 * Open Source Physics software is free software as described near the bottom of this code file.
 *
 * For additional information and documentation on Open Source Physics please see: 
 * <http://www.opensourcephysics.org/>
 */

package org.opensourcephysics.controls;

import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;

/**
 * A class to represent an encrypted version of a UTF-8-encoded String.
 * 
 * @author Doug Brown
 * @version 1.0 May 2006
 */
public class Cryptic {	
	
	// static fields
  private static String encoding = "UTF-8"; //$NON-NLS-1$
	private static String keyFormat = "PBEWithMD5AndDES"; //$NON-NLS-1$
	private static byte[] salt = {(byte)0x09, (byte)0x9C, (byte)0xC8, (byte)0x23,
			(byte)0x1E, (byte)0xAA, (byte)0xB3, (byte)0x41};
	private static int interactions = 19;
	private static final String DEFAULT_PW = "ospWCMBACBJDB"; //$NON-NLS-1$
	
	// instance fields
	private String cryptic; // the incrypted form of the input

  /**
	 * Protected no-arg constructor has null cryptic.
	 */
  protected Cryptic() {/** empty block */}

  /**
	 * Public constructor with input string.
	 * 
	 * @param input UTF-8 String to encrypt
	 */
  public Cryptic(String input) {
  	encrypt(input);
  }

  /**
	 * Public constructor with input and password.
	 * 
	 * @param input UTF-8 String to encrypt
	 */
  public Cryptic(String input, String password) {
  	encrypt(input, password);
  }

  /**
	 * Encrypts the input and saves in cryptic form.
	 * 
	 * @param input UTF-8 String to encrypt
	 * @return the encrypted content
	 */
	public String encrypt(String content) {
		return encrypt(content, DEFAULT_PW);
	}

	/**
	 * Encrypts the input with a password and saves in cryptic form.
	 * 
	 * @param input UTF-8 String to encrypt
	 * @return the encrypted content
	 */
	public String encrypt(String content, String password) {
    try {
			// create the key and parameter spec
			KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, interactions);
			SecretKey key = SecretKeyFactory.getInstance(keyFormat).generateSecret(keySpec);
			AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, interactions);
			// create the cipher
			Cipher ecipher = Cipher.getInstance(key.getAlgorithm());
			ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
			// get byte[] from content and encrypt with cipher
			byte[] bytes = content.getBytes(encoding);
			byte[] enc = ecipher.doFinal(bytes);
			// save encrypted bytes as string of chars 0-63
			// note this doubles the string length
			cryptic = new String(Base64Coder.encode(enc));
		}
		catch (Exception ex) {ex.printStackTrace();}  
		return cryptic;
	}
   
	/**
	 * Gets the decrypted string.
	 * @return the decrypted string
	 */
	public String decrypt() {
		return decrypt(DEFAULT_PW);
	}
 
	/**
	 * Gets the decrypted string using a password.
	 * 
	 * @param password the password
	 * @return the decrypted string
	 */
	public String decrypt(String password) {
    try {
			// create the key and parameter spec
			KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, interactions);
			SecretKey key = SecretKeyFactory.getInstance(keyFormat).generateSecret(keySpec);
			AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, interactions);
			// create the cipher
			Cipher dcipher = Cipher.getInstance(key.getAlgorithm());
			dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
			byte[] dec = null;
			try {
				dec = Base64Coder.decode(cryptic);
			}
			catch (IllegalArgumentException ex) {
				// decode legacy files encoded with sun encoder
				dec = new sun.misc.BASE64Decoder().decodeBuffer(cryptic);
			}
			byte[] bytes = dcipher.doFinal(dec);
			return new String(bytes, encoding);
		}
		catch (Exception ex) {ex.printStackTrace();}  	
		return null;
	}
 
	/**
	 * Gets the cryptic.
	 * @return the encrypted version of the input
	 */
	public String getCryptic() {
		return cryptic;
	}
 
	/**
	 * Sets the cryptic.
	 * @param encrypted an encrypted string
	 */
	public void setCryptic(String encrypted) {
		cryptic = encrypted;
	}
 
  /**
	 * Returns an ObjectLoader to save and load data for this class.
	 * 
	 * @return the object loader
	 */
  public static XML.ObjectLoader getLoader() {
    return new Loader();
  }

  /**
   * A class to save and load data for this class.
   */
  static class Loader implements XML.ObjectLoader {

    /**
     * Saves an object's data to an ObjectElement.
     *
     * @param control the control to save to
     * @param obj the object to save
     */
    public void saveObject(XMLControl control, Object obj) {
      Cryptic cryptic = (Cryptic)obj;
      control.setValue("cryptic", cryptic.getCryptic()); //$NON-NLS-1$
    }

    /**
     * Creates a new object.
     *
     * @param control the control
     * @return the newly created object
     */
    public Object createObject(XMLControl control) {
      return new Cryptic();
    }

    /**
     * Loads an object with data from an ObjectElement.
     *
     * @param control the control
     * @param obj the object
     * @return the loaded object
     */
    public Object loadObject(XMLControl control, Object obj) {
      Cryptic cryptic = (Cryptic)obj;
      cryptic.setCryptic(control.getString("cryptic")); //$NON-NLS-1$
      return obj;
    }
  }
}

